<?php

/**
 * SMTP发送邮件
 * 如果SMTP服务器需要验证的话，请通过setAuth方法设置用户名密码
 * 目前发送附件还有问题
 */
class SMTP {

    private $server; //服务器地址
    private $port; //服务器端口
    private $username; //登陆用户名
    private $password; //登陆密码
    private $helo; //发送方标识
    private $to; //接收人
    private $subject; //标题
    private $message; //内容
    private $cc; //抄送
    private $attachment; //附件
    private $charset; //字符集
    private $isHtml; //内容是否是html
    private $mailFrom; //发送人
    private $boundary; //多mime类型标识分割符
    private $contentType = "text/plain"; //内容类型

    /**
     *
     * @param string $server 服务器地址
     * @param int $port 端口号
     */

    public function __construct($server, $port=25) {
        $this->server = $server;
        $this->port = $port;
        $this->helo = __CLASS__;
        $this->attachment = array();
        $this->charset = 'utf-8';
        $this->isHtml = false;
        $this->boundary = uniqid(time());
    }

    /**
     * 设置发送者标识
     * @param string $helo 发送者表示
     */
    public function setHelo($helo) {
        $this->helo = $helo;
    }

    /**
     * 设置字符集
     * @param string $charset 字符集
     */
    public function setCharset($charset) {
        $this->charset = $charset;
    }

    /**
     * 设置验证信息
     * @param string $username 用户名
     * @param string $password 密码
     */
    public function setAuth($username, $password) {
        $this->username = $username;
        $this->password = $password;
    }

    /**
     *  设置要发送的附件的一个数组列表，请使用绝对路劲
     * @param array $files 文件数组
     */
    public function setAttachment($files) {
//        if (is_array($files))
//            $this->attachment = $files;
    }

    /**
     * 发送邮件
     * 多收件人，抄送人用逗号分隔
     * @param string $to 接收人
     * @param string $subject 标题
     * @param string $message 内容
     * @param string $mailfrom 发送人
     * @param bool $html 内容是否是html
     * @param string $cc 抄送人
     */
    public function send($to, $subject, $message, $mailfrom, $ishtml=0, $cc='') {
        $this->to = $to;
        $this->subject = $subject;
        $this->message = $message;
        $this->cc = $cc;
        $this->isHtml = $ishtml;
        $this->mailFrom = $mailfrom;

        $this->contentType = $this->isHtml ? "text/html" : "text/plain";

        $this->link = @fsockopen($this->server, $this->port, $errno, $errstr, 5);
        if (!$this->link)
            throw new SMTPException($errstr);

        //标记身份
        $lastmessage = fgets($this->link);
        $this->checkResponseCode($lastmessage, array(220));
        fputs($this->link, 'HELO ' . $this->helo . "\r\n");
        $lastmessage = fgets($this->link);
        $this->checkResponseCode($lastmessage, array(220, 250));

        //用户验证
        if ($this->username) {
            fputs($this->link, "AUTH LOGIN\r\n");
            $lastmessage = fgets($this->link);
            $this->checkResponseCode($lastmessage, array(334));

            fputs($this->link, base64_encode($this->username) . "\r\n");
            $lastmessage = fgets($this->link);
            $this->checkResponseCode($lastmessage, array(334));

            fputs($this->link, base64_encode($this->password) . "\r\n");
            $lastmessage = fgets($this->link);
            $this->checkResponseCode($lastmessage, array(235));
        }
        //发件人 
        fputs($this->link, "MAIL FROM: <$mailfrom>\r\n");
        $lastmessage = fgets($this->link);
        $this->checkResponseCode($lastmessage, array(250));

        //收件人
        fputs($this->link, "RCPT TO: <$to>\r\n");
        $lastmessage = fgets($this->link);
        $this->checkResponseCode($lastmessage, array(250));

        //发送邮件内容
        fputs($this->link, "DATA\r\n");
        $lastmessage = fgets($this->link);
        $this->checkResponseCode($lastmessage, array(354));

        //邮件内容
        $data.=$this->createHeader() . $this->createBody();
        $data.="\r\n.\r\n";

        fwrite($this->link, $data);

        $lastmessage = fgets($this->link);
        $this->checkResponseCode($lastmessage, array(250));
    }

    private function createHeader() {
        $header = array();
        $header[] = "Date: " . date('r') . "\r\n";

        $header[] = "To: <" . $this->to . ">\r\n";
        $header[] = "Subject: " . $this->subject . "\r\n";
        $header[] = "Message-ID: <" . uniqid($this->mailFrom) . ">\r\n";
        //html邮件
        if (!empty($this->attachment)) {
            $header[] = "Content-Type: Multipart/Mixed;charset=" . $this->charset . ";boundary=\"Boundary-=" . $this->boundary . "\"\r\n";
        } elseif ($this->isHtml) {
            $header[] = "Content-Type: " . $this->contentType . ";charset=" . $this->charset . "\r\n";
        } else {
            $header[] = "Content-Type: text/plain;charset=" . $this->charset . "\r\n";
        }
        $header[].="MIME-Version: 1.0\r\n\r\n";
        return implode('', $header);
    }

    private function createBody() {
        if (!empty($this->attachment)) {
            return $this->createAttach();
        } else {
            return $this->message;
        }
    }

    private function createAttach() {
        $mime = array();
        $mime[] = sprintf("--Boundary-=%s\r\n", $this->boundary);
        $mime[] = sprintf("Content-Type: %s\r\n", $this->contentType);
        $mime[] = "Content-Transfer-Encoding: 8bit\r\n";
        $mime[] = sprintf("%s\r\n\r\n", $this->message);

        foreach ($this->attachment as $file) {
            if (file_exists($file)) {
                $basename = basename(realpath($file));
                $fcontent = base64_encode(file_get_contents($file));
                $mime[] = sprintf("--Boundary-=%s\r\n", $this->boundary);
                $mime[] = "Content-Type: application/octet-stream;name=$basename";
                $mime[] = "Content-Transfer-Encoding: base64\r\n";
                $mime[] = sprintf("Content-Disposition: attachment; filename=\"%s\"\r\n\r\n", $basename);
                $mime[] = sprintf("%s\r\n\r\n", $fcontent);
            }
        }
        $mime[] = sprintf("\r\n--Boundary-=%s--\r\n", $this->boundary);
        return implode('', $mime);
    }

    /**
     *
     * @param string $message 信息内容
     * @param array $code 要检查的代码
     */
    private function checkResponseCode($message, $code) {
        $c = implode('|', $code);
        if (!preg_match("/^$c/", $message, $matches))
            throw new SMTPException($message);
        return $matches[0];
    }

    public function close() {
        if ($this->link) {
            fputs($this->link, "QUIT\r\n");
            fclose($this->link);
            $this->link = false;
        }
    }

    public function __destruct() {
        $this->close();
    }

}

class SMTPException extends Exception {

    public function __construct($message) {
        parent::__construct($message);
    }

}

?>
